
#include "gh.h"

static const char *nVertexProgramSrc =
    "!!NVvp4.0"
    NL "FLOAT PARAM ModelviewProj[4] = { program.local[0..3] };"
    NL
    NL "main:"
    NL "    DP4.F result.position.x, vertex.attrib[0], ModelviewProj[0];"
    NL "    DP4.F result.position.y, vertex.attrib[0], ModelviewProj[1];"
    NL "    DP4.F result.position.z, vertex.attrib[0], ModelviewProj[2];"
    NL "    DP4.F result.position.w, vertex.attrib[0], ModelviewProj[3];"
    NL "    MOV.F result.attrib[0], vertex.attrib[1];"
    NL "    RET;"
    NL "END";

static const char *nFragmentProgramSrc =
    "!!NVfp4.0"
    NL
    NL "main:"
    NL "    MOV.F result.color, fragment.attrib[0];"
    NL "    RET;"
    NL "END";

static GLuint nProgram[2] = { 0 };
static GLuint nVBO = 0;
static GLuint64 nVBOAddress;

#define VERTEX_SIZE (5 * sizeof(float))

int ghAppBegin() {
    if (!GLEW_NV_gpu_program4) {
        GH_ERROR_SET("Program requires GL_NV_gpu_program4 extension.");
        return -1;
    }

    /* create programs */
    glGenProgramsARB(2, nProgram);

    glBindProgramARB(GL_VERTEX_PROGRAM_ARB, nProgram[0]);
    if (ghNVProgramSetString(GL_VERTEX_PROGRAM_ARB, nVertexProgramSrc) < 0) {
        return -1;
    }

    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, nProgram[1]);
    if (ghNVProgramSetString(GL_FRAGMENT_PROGRAM_ARB, nFragmentProgramSrc) < 0) {
        return -1;
    }

    /* create vbo */ {
        float vertices[] = {
            -1.0f, -1.0f, 1.0f, 0.0f, 0.0f,
            1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
            0.0f, 1.0f, 0.0f, 0.0f, 1.0f
        };

        glGenBuffers(1, &nVBO);
        glBindBuffer(GL_ARRAY_BUFFER, nVBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        glGetBufferParameterui64vNV(GL_ARRAY_BUFFER, GL_BUFFER_GPU_ADDRESS_NV, &nVBOAddress);
        glMakeBufferResidentNV(GL_ARRAY_BUFFER, GL_READ_ONLY);
    }

    /* set GL state */
    glEnable(GL_VERTEX_PROGRAM_ARB);
    glEnable(GL_FRAGMENT_PROGRAM_ARB);

    return 0;
}

int ghAppFrame(double dt, double time) {
    /* compute matrices */
    GHmat m, mvp;
    GHvec eyePos, lookPos, upDir;

    eyePos = ghVecSet(0.f, 0.f, 5.f, 1.f);
    lookPos = ghVecSet(0.f, 0.f, 0.f, 1.f);
    upDir = ghVecSet(0.f, 1.f, 0.f, 0.f);

    mvp = ghArcBallGetMat();
    m = ghMatLookAt(eyePos, lookPos, upDir);
    mvp = ghMatMul(&mvp, &m);
    m = ghMatPerspective(GH_PIDIV4, 1.0f, 1.0f, 15.0f);
    mvp = ghMatMul(&mvp, &m);
    mvp = ghMatTranspose(&mvp);

    /* clear buffers */
    glClear(GL_COLOR_BUFFER_BIT);

    /* set OpenGL programs */
    glBindProgramARB(GL_VERTEX_PROGRAM_ARB, nProgram[0]);
    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, nProgram[1]);

    /* set rotation matrix */
    glProgramLocalParameters4fvEXT(GL_VERTEX_PROGRAM_ARB, 0, 4, &mvp._00);

    /* draw */
    glEnableClientState(GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV);
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glVertexAttribFormatNV(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float));
    glVertexAttribFormatNV(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float));
    glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, 0, nVBOAddress, 3 * VERTEX_SIZE);
    glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, 1, nVBOAddress + sizeof(float) * 2, 3 * VERTEX_SIZE - sizeof(float) * 2);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    return 0;
}

int ghAppEnd() {
    glDeleteProgramsARB(2, nProgram);
    glDeleteBuffers(1, &nVBO);

    return 0;
}

int ghAppMouseCallback(int x, int y, int action) {
    switch (action) {
    case GH_MOUSE_DOWN:
        ghArcBallBegin(x, y);
        break;

    case GH_MOUSE_UP:
        ghArcBallEnd();
        break;

    case GH_MOUSE_MOVE:
        ghArcBallMove(x, y);
        break;
    }

    return 0;
}
